[毎日Kotlin] Day41. Builders: how it works
はじめに
毎日Kotlinシリーズです。
このシリーズを初めての方はこちらです。「毎日Kotlin」はじめました | Developers.IO
問題
Builders how it works | Try Kotlin
Look at the questions below and give your answers
- In the Kotlin code
tr { td { text("Product") } td { text("Popularity") } }
'td' is:
a. special built-in syntactic construct
b. function declaration
c. function invocation
- In the Kotlin code
tr (color = "yellow") { td { text("Product") } td { text("Popularity") } }
'color' is:
a. new variable declaration
b. argument name
c. argument value
- The block
{ text("Product") }
from the previous question is:
a. block inside built-in syntax construction td
b. function literal (or "lambda")
c. something mysterious
- For the code
tr (color = "yellow") { this.td { text("Product") } td { text("Popularity") } }
which of the following is true:
a. this code doesn't compile
b. this refers to an instance of an outer class
c. this refers to a receiver parameter TR of the function literal:
tr (color = "yellow") { this@tr.td { text("Product") } }
import Answer.* enum class Answer { a, b, c } val answers = mapOf<Int, Answer?>( 1 to null, 2 to null, 3 to null, 4 to null )
狙い
ここで考えて欲しい問題の意図はなんだろうか?
Kotlinの豊かなDSL。使いこなすと強力な武器。いままで解いてきた問題の集大成!
解答例
1 to c, 2 to b, 3 to b, 4 to c
mapOfをtoを記述できるようにしたものです。
toはただの拡張関数でPairを作ります。
public infix fun <A, B> A.to(that: B): kotlin.Pair<A, B>
本来なら
1.to(Answer.a)
なんですが、infixがついているので、スペース区切りで呼べます。
1 to c
種はこれだけなんですが、簡潔な記述に見えますね!
HtmlBuilderの補足
[毎日Kotlin] Day40. Html builderの解説が本日の問題説明文であります。
Html builders | Try Kotlinの定義はこちらを参照。
return html { table { tr { td { text("Product") } td { text("Price") } td { text("Popularity") } } } }.toString()
頭から見ていきましょう。
fun html(init: Html.() -> Unit): Html = Html().apply(init)
Html()をnewして、一時的な拡張関数initを引数でもらうことで、Htmlの初期化処理を外からできるようにしています。html {}の内部のthisはHtmlオブジェクトのことです。
tableの定義を見てみよう。
fun Html.table(init : Table.() -> Unit) = doInit(Table(), init) class Table: Tag("table") fun <T: Tag> Tag.doInit(tag: T, init: T.() -> Unit): T { tag.init() children.add(tag) return tag } open class Tag(val name: String) { val children = mutableListOf<Tag>() val attributes = mutableListOf<Attribute>() override fun toString(): String { return "<$name" + (if (attributes.isEmpty()) "" else attributes.joinToString(separator = " ", prefix = " ")) + ">" + (if (children.isEmpty()) "" else children.joinToString(separator = "")) + "</$name>" } }
Htmlの拡張関数としてtableが定義されています。
Htmlと同じようにTable初期化処理を一時的な拡張関数initでもらっています。 それをHtmlの子要素に入れています。
tr,tdも同じように、自身を外部から初期化処理をもらい、親のchildernに自身を追加する流れになっています。
最後のtoString()のときに自分と子要素を結合していっています。
open class Tag(val name: String) { val children = mutableListOf<Tag>() val attributes = mutableListOf<Attribute>() override fun toString(): String { return "<$name" + (if (attributes.isEmpty()) "" else attributes.joinToString(separator = " ", prefix = " ")) + ">" + (if (children.isEmpty()) "" else children.joinToString(separator = "")) + "</$name>" } }
やってることはそんなに難しいことをしていませんが、ラムダと拡張関数のオンパレードでややこしくみえます。その分、使用者は簡潔に、適切に記述できるようになります。
DSLでよく使えれる機能もチェック
いつも参考にさせていただいております。ありがとうございます!
あとがき
Day42.でまたお会いしましょう。 そして最終回。